package lab.cadl.lirui.markdown.core.renderers

import lab.cadl.lirui.markdown.core.model.*
import lab.cadl.lirui.markdown.core.model.Text
import lab.cadl.lirui.markdown.core.utils.HlTokenType
import lab.cadl.lirui.markdown.core.utils.LatexUtils
import lab.cadl.lirui.markdown.core.utils.highlightCode
import org.docx4j.Docx4J
import org.docx4j.XmlUtils
import org.docx4j.jaxb.Context
import org.docx4j.openpackaging.packages.WordprocessingMLPackage
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage
import org.docx4j.openpackaging.parts.WordprocessingML.DocumentSettingsPart
import org.docx4j.openpackaging.parts.WordprocessingML.FootnotesPart
import org.docx4j.openpackaging.parts.WordprocessingML.NumberingDefinitionsPart
import org.docx4j.openpackaging.parts.relationships.Namespaces
import org.docx4j.toc.TocGenerator
import org.docx4j.wml.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.File
import java.math.BigInteger
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.*
import javax.imageio.ImageIO


class WordOption : CustomOption {
    var useSectionImageCounter: Boolean = true
    var maxBlockImageWidth: Int? = null //4000
    var minBlockImageWidth: Int? = 5000
    var formulaAsImage: Boolean = true
    var enableCodeHighlight: Boolean = true
}

class WordVisitor(private val wmlPackage: WordprocessingMLPackage,
                  private val option: RenderOption) : AbstractVisitor() {
    companion object {
        const val NUMBER_NUM_ID: Long = 1
        const val BULLET_NUM_ID: Long = 2

        val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
                .withLocale(Locale.CHINA)
                .withZone(ZoneId.systemDefault())!!

        val LOG: Logger = LoggerFactory.getLogger(WordVisitor::class.java)

        var footnotePartXML = ("<w:footnotes mc:Ignorable=\"w14 wp14\" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:xml=\"http://www.w3.org/XML/1998/namespace\" xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\">"
                + "<w:footnote w:id=\"-1\" w:type=\"separator\">"  // matching CTFtnDocProps above
                + "<w:p>"
                + "<w:pPr>"
                + "<w:spacing w:after=\"0\" w:line=\"240\" w:lineRule=\"auto\"/>"
                + "</w:pPr>"
                + "<w:r>"
                + "<w:separator/>"
                + "</w:r>"
                + "</w:p>"
                + "</w:footnote>"
                + "<w:footnote w:id=\"0\" w:type=\"continuationSeparator\">"
                + "<w:p>"
                + "<w:pPr>"
                + "<w:spacing w:after=\"0\" w:line=\"240\" w:lineRule=\"auto\"/>"
                + "</w:pPr>"
                + "<w:r>"
                + "<w:continuationSeparator/>"
                + "</w:r>"
                + "</w:p>"
                + "</w:footnote>"
                + "</w:footnotes>")
    }

    private val mainDoc = wmlPackage.mainDocumentPart
    private val wordOption = option.customOption as WordOption
    private var currentP: ContentAccessor? = null
    private val objectFactory: ObjectFactory = Context.getWmlObjectFactory()
    private val reFactory: org.docx4j.relationships.ObjectFactory = org.docx4j.relationships.ObjectFactory()
    private val imageCache = option.imageCache
    private var numId: Long? = NUMBER_NUM_ID
    private var bulletNumId: Long? = BULLET_NUM_ID
    private var inLink: Boolean = false
    private var inCell: Boolean = false
    private var paragraphAlign: JcEnumeration? = null
    private var lastSection: Section? = null

    private var currentContainer: ContentAccessor? = null

    var tocIndex: Int = -1

    init {
        initNumbering()
        initFootnote()
    }

    private fun appendDocObject(o: Any) {
        if (currentContainer == null) {
            mainDoc.addObject(o)
        } else {
            currentContainer!!.content.add(o)
        }
    }

    private fun initNumbering() {
        if (mainDoc.numberingDefinitionsPart == null) {
            LOG.info("INIT numbering")
            val part = NumberingDefinitionsPart()
            part.jaxbElement = XmlUtils.unmarshalString(javaClass.classLoader.getResourceAsStream("lab/cadl/lirui/markdown/core/template-numbering.xml")!!.bufferedReader(StandardCharsets.UTF_8).readText()) as Numbering
            mainDoc.addObject(part)
        }
    }

    private fun initFootnote() {
        if (mainDoc.footnotesPart == null) {
            LOG.info("INIT footnote")
            val footnotesPart = FootnotesPart()
            mainDoc.addTargetPart(footnotesPart)

            val footnotes = XmlUtils.unwrap(
                    XmlUtils.unmarshalString(footnotePartXML)) as CTFootnotes
            footnotesPart.jaxbElement = footnotes

            // Usually the settings part contains footnote properties;
            // so add these if not present
            var dsp = mainDoc.documentSettingsPart
            if (dsp == null) {
                // create it
                dsp = DocumentSettingsPart()
                mainDoc.addTargetPart(dsp)
            }
            var settings: CTSettings? = dsp.contents
            if (settings == null) {
                settings = objectFactory.createCTSettings()
                dsp.jaxbElement = settings
            }

            val props = settings!!.footnotePr
            if (props == null) {
                val openXML = ("<w:footnotePr xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">"
                        + "<w:footnote w:id=\"-1\"/>" // these 2 numbers are special, and correspond with string footnotePartXML below
                        + "<w:footnote w:id=\"0\"/>"
                        + "</w:footnotePr>")
                settings.footnotePr = XmlUtils.unmarshalString(openXML, Context.jc, CTFtnDocProps::class.java) as CTFtnDocProps
            }
        }
    }

    override fun visit(document: MarkdownDocument) {
        mainDoc.addStyledParagraphOfText(resolveStyleId("Title"), document.prop(MarkdownConstants.TitleProperty))
        mainDoc.addStyledParagraphOfText(resolveStyleId("Subtitle"), document.prop(MarkdownConstants.SubTitleProperty))
        super.visit(document)
    }

    override fun visit(section: Section) {
        lastSection = section

        if (section.heading != null) {
            mainDoc.addStyledParagraphOfText(resolveStyleId("heading ${section.level}"),
                    "${section.fullSeqString()} ${section.heading?.title()}")
        }

        visitChildren(section)
    }

    private fun resolveStyleId(id: String): String {
        val idResolved = mainDoc.styleDefinitionsPart.getIDForStyleName(id)
        return idResolved ?: id
    }

    override fun visit(paragraph: Paragraph) {
        assert(currentP == null)

        withP(createStyledParagraph(if (inCell) {
            null
        } else if (lastSection is AbstractSection) {
            "摘要正文"
        } else {
            "缩进正文"
        }, hAlign = paragraphAlign)) {
            appendDocObject(it)
            visitChildren(paragraph)
        }
    }

    override fun visit(orderedList: OrderedList) {
        if (orderedList.parent is Section) {
            numId = mainDoc.numberingDefinitionsPart.restart(numId!!, 0, orderedList.startNumber.toLong())
        }

        visitChildren(orderedList)
    }

    override fun visit(bulletList: BulletList) {
        visitChildren(bulletList)
    }

    override fun visit(listItem: ListItem) {
        val p = createStyledParagraph(if (inCell) {
            null
        } else {
            "缩进正文"
        }, hAlign = paragraphAlign)
        appendDocObject(p)

        val ind = objectFactory.createPPrBaseInd()
        p.pPr.ind = ind
        ind.firstLineChars = if (inCell) {
            BigInteger.valueOf(0)
        } else {
            BigInteger.valueOf(2)
        }

        val numPr = objectFactory.createPPrBaseNumPr()
        p.pPr.numPr = numPr

        numPr.ilvl = objectFactory.createPPrBaseNumPrIlvl()
        numPr.ilvl.`val` = listItem.parentList.level.toBigInteger()

        numPr.numId = objectFactory.createPPrBaseNumPrNumId()
        if (listItem.parent is OrderedList) {
            numPr.numId.`val` = numId!!.toBigInteger()
        } else {
            numPr.numId.`val` = bulletNumId!!.toBigInteger()
        }

        withP(p) {
            visitChildren(listItem)
        }
    }

    override fun visit(fencedCodeBlock: FencedCodeBlock) {
        // 题注
        addParagraph(createTextR(caption(fencedCodeBlock)), styleId = "caption")

        // 代码，在一个单格表中
        val table = createTable()

        val row = objectFactory.createTr()
        table.content.add(row)

        val cell = createTc()
        row.content.add(cell)

        if (wordOption.enableCodeHighlight) {
            val codeBlock = highlightCode(fencedCodeBlock.content, fencedCodeBlock.lang)
            codeBlock.lines.forEachIndexed { index, line ->
                val paragraph = createStyledParagraph("代码")
                cell.content.add(paragraph)

                paragraph.content.add(createTextR("${index + 1}.  ", colorValue = "555555", fontSize = 10))

                for (token in line.tokens) {
                    val r = when (token.type) {
                        HlTokenType.NORMAL -> createTextR(token.content)
                        HlTokenType.KEYWORD -> createTextR(token.content, colorValue = "569CCA")
                        HlTokenType.NUMBER -> createTextR(token.content, colorValue = "B5C077")
                        HlTokenType.STRING -> createTextR(token.content, colorValue = "CE9178")
                        HlTokenType.COMMENT -> createTextR(token.content, colorValue = "6A9955")
                        else -> createTextR(token.content)
                    }

                    paragraph.content.add(r)
                }
            }
        } else {
            val lines = fencedCodeBlock.content.split("\n")
            lines.forEachIndexed { index, line ->
                val paragraph = createStyledParagraph("代码")
                cell.content.add(paragraph)

                paragraph.content.add(createTextR("${index + 1}.  ", colorValue = "555555", fontSize = 10))
                paragraph.content.add(createTextR(line))
            }
        }

        mainDoc.addObject(table)
    }

    private fun createTable(withBorders: Boolean = true): Tbl {
        val table = objectFactory.createTbl()
        val tablePr = TblPr()
        if (withBorders) {
            tablePr.tblBorders = createTableBorders()
        }

        tablePr.tblW = tableWidth(pageWidth())
        table.tblPr = tablePr

        return table
    }

    private fun pageWidth(): BigInteger {
        val sections = wmlPackage.documentModel.sections
        val page = sections[sections.size - 1].pageDimensions
        return page.pgSz.w - (page.pgMar.left + page.pgMar.right)
    }

    private fun tableWidth(width: BigInteger): TblWidth {
        val tableWidth = TblWidth()
        tableWidth.w = width
        return tableWidth
    }

    private fun createTableBorders(): TblBorders {
        val border = CTBorder()
        border.color = "black"
        border.sz = BigInteger("15")
        border.space = BigInteger("0")
        border.`val` = STBorder.SINGLE

        val borders = TblBorders()
        borders.top = border
        borders.bottom = border
        return borders
    }

    private fun createHeaderBorders(hasTop: Boolean = false): TcPrInner.TcBorders {
        val border = CTBorder()
        border.color = "black"
        border.sz = BigInteger("10")
        border.space = BigInteger("0")
        border.`val` = STBorder.SINGLE

        val borders = TcPrInner.TcBorders()
        if (hasTop) {
            borders.top = border
        }

        borders.bottom = border
        return borders
    }

    override fun visit(imageBlock: ImageBlock) {
        addParagraph(createImageR(imageBlock.url, inline = false,
                maxWidth = wordOption.maxBlockImageWidth,
                minWidth = wordOption.minBlockImageWidth),
                styleId = "图表")
        addParagraph(createTextR(caption(imageBlock)), styleId = "caption")
    }

    private fun createStyledParagraph(styleId: String? = null, hAlign: JcEnumeration? = null): P {
        val p = objectFactory.createP()
        val pPr = objectFactory.createPPr()
        p.pPr = pPr

        if (styleId != null) {
            val resolvedStyleId = resolveStyleId(styleId)
            if (mainDoc.propertyResolver.activateStyle(resolvedStyleId)) {
                val pStyle = objectFactory.createPPrBasePStyle()
                pPr.pStyle = pStyle
                pStyle.`val` = resolvedStyleId
            }
        }

        if (hAlign != null) {
            val jc = objectFactory.createJc()
            pPr.jc = jc

            jc.`val` = hAlign
        }

        return p
    }

    private fun addParagraph(r: R, styleId: String? = null) {
        val p = createStyledParagraph(styleId)
        p.content.add(r)
        mainDoc.addObject(p)
    }

    override fun visit(blockQuote: BlockQuote) {
        withP(createStyledParagraph("引用内容")) {
            mainDoc.addObject(it)
            visitChildren(blockQuote)
        }

        if (blockQuote.source != null) {
            val paragraph = createStyledParagraph("引用来源")
            mainDoc.addObject(paragraph)
            paragraph.content.add(createTextR("—— ${blockQuote.source}"))
        }
    }

    override fun visit(tableBlock: TableBlock) {
        // table caption
        if (!wordOption.useSectionImageCounter || tableBlock.rootSection?.seq != null) {
            addParagraph(createTextR(caption(tableBlock)), styleId = "caption")
        }

        // add table
        val table = createTable()
        var firstHeader = true

        for (rowBlock in tableBlock.rows) {
            val row = objectFactory.createTr()
            table.content.add(row)

            for (cellBlock in rowBlock.cells) {
                val cell = createTc()
                row.content.add(cell)

                if (rowBlock.isHeader) {
                    cell.tcPr.tcBorders = createHeaderBorders(!firstHeader)
                }

                if (cellBlock.children.any { it is Inline } || cellBlock.children.none { it is Block }) {
                    withP(createStyledParagraph(hAlign = alignment(cellBlock.horizonAlign))) { p ->
                        cell.content.add(p)

                        cellBlock.children.filter { it is Inline }.forEach { c -> c.accept(this) }
                    }
                }

                inCell = true
                val prevAlign = paragraphAlign
                paragraphAlign = alignment(cellBlock.horizonAlign)
                withContainer(cell) {
                    cellBlock.children.filter { it is Block }.forEach { it.accept(this) }
                }

                paragraphAlign = prevAlign
                inCell = false
            }

            if (rowBlock.isHeader && firstHeader) {
                firstHeader = false
            }
        }

        mainDoc.addObject(table)
    }

    private fun withContainer(container: ContentAccessor, callback: () -> Unit) {
        val prevContainer = currentContainer
        currentContainer = container
        callback()
        currentContainer = prevContainer
    }

    private fun createTc(): Tc {
        val cell = objectFactory.createTc()

        val tcPr = objectFactory.createTcPr()
        cell.tcPr = tcPr

        val vAlign = objectFactory.createCTVerticalJc()
        tcPr.vAlign = vAlign
        vAlign.`val` = STVerticalJc.CENTER


        return cell
    }

    private fun alignment(hAlign: HorizonAlign): JcEnumeration {
        return when (hAlign) {
            HorizonAlign.LEFT -> JcEnumeration.LEFT
            HorizonAlign.RIGHT -> JcEnumeration.RIGHT
            HorizonAlign.MIDDLE -> JcEnumeration.CENTER
            HorizonAlign.BOTH -> JcEnumeration.BOTH
        }
    }

    private fun withP(p: ContentAccessor, callback: (p: ContentAccessor) -> Unit) {
        val prevP = currentP
        currentP = p
        callback(p)
        currentP = prevP
    }

    override fun visit(text: Text) {
        assert(currentP != null)

        val r = createTextR(text.content)
        currentP!!.content.add(r)
    }

    private fun createBooleanDefaultTrue(value: Boolean): BooleanDefaultTrue {
        val b = BooleanDefaultTrue()
        b.isVal = value
        return b
    }

    private fun createTextR(content: String,
                            bold: Boolean = false,
                            italy: Boolean = false,
                            caps: Boolean = false,
                            strike: Boolean = false,
                            colorValue: String? = null,
                            fontSize: Int? = null,
                            superscript: Boolean = false,
                            fontName: String? = null): R {
        val r = createR(bold, italy, caps, strike, colorValue, fontSize, superscript, fontName)
        val t = objectFactory.createText()
        t.value = content
        t.space = "preserve"

        r.content.add(t)
        return r
    }

    private fun createR(bold: Boolean = false,
                        italy: Boolean = false,
                        caps: Boolean = false,
                        strike: Boolean = false,
                        colorValue: String? = null,
                        fontSize: Int? = null,
                        superscript: Boolean = false,
                        fontName: String? = null): R {
        val r = objectFactory.createR()

        val rpr = objectFactory.createRPr()
        rpr.b = createBooleanDefaultTrue(bold)
        rpr.i = createBooleanDefaultTrue(italy)
        rpr.caps = createBooleanDefaultTrue(caps)
        rpr.strike = createBooleanDefaultTrue(strike)
        if (inLink) {
            val u = objectFactory.createU()
            u.`val` = UnderlineEnumeration.SINGLE
            rpr.u = u
        }

        if (colorValue != null) {
            val color = objectFactory.createColor()
            color.`val` = colorValue
            rpr.color = color
        }

        if (fontSize != null) {
            val size = objectFactory.createHpsMeasure()
            size.`val` = fontSize.toBigInteger()
            rpr.sz = size
        }

        if (superscript) {
            val vertAlign = objectFactory.createCTVerticalAlignRun()
            vertAlign.`val` = STVerticalAlignRun.SUPERSCRIPT
            rpr.vertAlign = vertAlign
        }

        if (fontName != null) {
            val fonts = objectFactory.createRFonts()
            rpr.rFonts = fonts
            fonts.ascii = fontName
            fonts.hAnsi = fontName
            fonts.cs = fontName

        }

        r.rPr = rpr

        return r
    }

    private fun createImageR(url: String, inline: Boolean, maxWidth: Int? = null, minWidth: Int? = null, scale: Double? = null): R {
        val imageEntry = imageCache.fetchImage(url)
        val content = Files.readAllBytes(imageEntry.file.toPath())
        val imagePart = BinaryPartAbstractImage.createImagePart(wmlPackage, content)

        var realMaxWidth = maxWidth ?: Int.MAX_VALUE
        val input = ByteArrayInputStream(content)
        val imageO = ImageIO.read(input)

        val w = if (scale != null) {
            (imageO.width * scale * 34 / LatexUtils.SCALE).toInt()
        } else {
            (imageO.width * 7.2).toInt()
        }

        realMaxWidth = Math.min(realMaxWidth, w)
        if (minWidth != null) {
            realMaxWidth = Math.max(realMaxWidth, minWidth)
        }

        val image = imagePart.createImageInline("", "", imageEntry.id1, imageEntry.id2, false, realMaxWidth)

        val r = objectFactory.createR()
        val drawing = objectFactory.createDrawing()
        drawing.anchorOrInline.add(image)
        r.content.add(drawing)

        if (inline) {
            val rpr = objectFactory.createRPr()
            r.rPr = rpr

            val position = objectFactory.createCTSignedHpsMeasure()
            rpr.position = position

            position.`val` = BigInteger.valueOf(-6)
        }

        return r
    }

    override fun visit(image: Image) {
        val imageE = createImageR(image.url, inline = true)
        currentP!!.content.add(imageE)
    }

    private fun createCodeR(codeText: String): R {
        return createTextR(codeText, bold = true, fontSize = 21, colorValue = "0070C0", fontName = "Consolas")
    }

    override fun visit(code: Code) {
        val run = createCodeR(code.content)
        currentP!!.content.add(run)
    }

    private fun createFormulaImage(formulaText: String) {
        val file = imageCache.registerImage(formulaText, ".png")
        LatexUtils.saveFormulaToImage(formulaText, file)
    }

    override fun visit(formula: Formula) {
        val run = if (wordOption.formulaAsImage) {
            createFormulaImage(formula.content)
            createImageR(formula.content, inline = true, scale = 0.5)
        } else {
            createCodeR(formula.content)
        }
        currentP!!.content.add(run)
    }

    override fun visit(emphasis: Emphasis) {
        val run = createTextR(emphasis.content, bold = true)
        currentP!!.content.add(run)
    }

    override fun visit(deleteText: DeleteText) {
        val run = createTextR(deleteText.content, strike = true)
        currentP!!.content.add(run)
    }

    override fun visit(italyText: ItalyText) {
        val run = createTextR(italyText.content, italy = true)
        currentP!!.content.add(run)
    }

    override fun visit(urlLink: UrlLink) {
        val rel = reFactory.createRelationship()
        rel.type = Namespaces.HYPERLINK
        rel.target = urlLink.url
        rel.targetMode = "External"
        mainDoc.relationshipsPart.addRelationship(rel)

        val hyperLink = objectFactory.createPHyperlink()
        hyperLink.id = rel.id

        currentP!!.content.add(hyperLink)

        withP(hyperLink) {
            inLink = true
            visitChildren(urlLink)
            inLink = false
        }
    }

    override fun visit(entityText: EntityText) {
        val run = createTextR(entityText.content, fontName = "Cambria Math")
        currentP!!.content.add(run)
    }

    override fun visit(footnote: Footnote) {
        val r = createR(superscript = true)
        currentP!!.content.add(r)

        val ref = objectFactory.createCTFtnEdnRef()
        val wrapped = objectFactory.createRFootnoteReference(ref)
        r.content.add(wrapped)

        val id = footnote.documentCounter.toBigInteger()
        ref.id = id

        val def = objectFactory.createCTFtnEdn()
        def.id = id

        val p = objectFactory.createP()
        def.content.add(p)

        val ppr = objectFactory.createPPr()
        p.pPr = ppr

        val pStyle = objectFactory.createPPrBasePStyle()
        ppr.pStyle = pStyle
        pStyle.`val` = "FootnoteText"

        val numR = objectFactory.createR()
        p.content.add(numR)
        val rpr = objectFactory.createRPr()
        numR.rPr = rpr

        val rStyle = objectFactory.createRStyle()
        rpr.rStyle = rStyle
        rStyle.`val` = "FootnoteReference"

        val footnoteRef = objectFactory.createRFootnoteRef()
        numR.content.add(footnoteRef)

        p.content.add(createTextR(" "))

        withP(p) {
            visitChildren(footnote)
        }

        mainDoc.footnotesPart.contents.footnote.add(def)
    }

    override fun visit(refLink: RefLink) {
        val target = refLink.document!!.resolveRef(refLink.ref)

        val element = target as AbstractElement
        val seqString = when {
            target.fixSeq != null -> {
                target.fixSeq
            }
            wordOption.useSectionImageCounter -> {
                "${element.rootSection?.seq}-${element.sectionCounter}"
            }
            else -> {
                "${element.documentCounter}"
            }
        }

        currentP!!.content.add(createTextR("${target.refLabel} $seqString"))
    }

    private fun seqString(element: AbstractElement): String {
        return if (wordOption.useSectionImageCounter) {
            "${element.rootSection?.seq}-${element.sectionCounter}"
        } else {
            "${element.documentCounter}"
        }
    }

    private fun caption(refTarget: RefTarget): String {
        val seqString = seqString(refTarget as AbstractElement)

        return "${refTarget.refLabel} $seqString  ${refTarget.title}"
    }

    override fun visit(algorithmBlock: AlgorithmBlock) {
        // 题注
        addParagraph(createTextR(caption(algorithmBlock)), styleId = "caption")

        // 代码，在一个单格表中
        val table = createTable()
        mainDoc.addObject(table)

        algorithmBlock.metaList.forEachIndexed { index, meta ->
            val isLast = index == algorithmBlock.metaList.size - 1

            val row = objectFactory.createTr()
            table.content.add(row)

            val cell = createTc()
            row.content.add(cell)

            if (isLast) {
                cell.tcPr.tcBorders = createHeaderBorders()
            }

            withP(createStyledParagraph()) {
                cell.content.add(it)

                meta.accept(this)
            }
        }

        algorithmBlock.lines.forEachIndexed { index, line ->
            val row = objectFactory.createTr()
            table.content.add(row)

            val cell = createTc()
            row.content.add(cell)

            withP(createStyledParagraph("代码")) {
                cell.content.add(it)
                it.content.add(createTextR("${index + 1}: "))

                visitChildren(line)
            }
        }
    }

    override fun visit(algorithmInput: AlgorithmInput) {
        val p = currentP!!
        p.content.add(createTextR("[IN] "))
        p.content.add(createTextR(algorithmInput.name, fontName = "Consolas"))
        p.content.add(createTextR(" : "))
        visitChildren(algorithmInput)
    }

    override fun visit(algorithmOutput: AlgorithmOutput) {
        val p = currentP!!
        p.content.add(createTextR("[OUT] "))
        p.content.add(createTextR(algorithmOutput.name, fontName = "Consolas"))
        p.content.add(createTextR(" : "))
        visitChildren(algorithmOutput)
    }

    override fun visit(formulaBlock: FormulaBlock) {
        val table = createTable(withBorders = false)
        mainDoc.addObject(table)

        val seqString = seqString(formulaBlock)

        val row = objectFactory.createTr()
        table.content.add(row)

        val emptyCell = createTc()
        row.content.add(emptyCell)

        withP(createStyledParagraph(hAlign = JcEnumeration.LEFT)) {
            emptyCell.content.add(it)
            it.content.add(createTextR("($seqString)", colorValue = "white"))
        }

        val formulaCell = createTc()
        row.content.add(formulaCell)

        withP(createStyledParagraph(hAlign = JcEnumeration.CENTER)) {
            formulaCell.content.add(it)

            formulaBlock.formula.accept(this)
        }

        val numberCell = createTc()
        row.content.add(numberCell)

        withP(createStyledParagraph(hAlign = JcEnumeration.RIGHT)) {
            numberCell.content.add(it)
            it.content.add(createTextR("($seqString)"))
        }
    }

    override fun visit(paperRef: PaperRef) {
        val document = paperRef.document!!
        val index = document.resolvePaperRefIndex(paperRef.refId) + 1
        currentP!!.content.add(createTextR("[$index]", superscript = true))
    }

    override fun visit(refSection: RefSection) {
        mainDoc.addStyledParagraphOfText(resolveStyleId("heading 1"), "参考文献")

        val document = refSection.document!!
        document.loadRefEntries().forEachIndexed { index, entry ->
            val i = index + 1
            mainDoc.addStyledParagraphOfText(resolveStyleId("参考文献"), "[$i] ${entry.gbString()}")
        }
    }

    override fun visit(abstractSection: AbstractSection) {
        lastSection = abstractSection

        val p = createStyledParagraph("heading 1", hAlign = JcEnumeration.CENTER)
        p.content.add(createTextR(if (abstractSection.isChinese) {
            "摘要"
        } else {
            "Abstract"
        }, bold = true))
        mainDoc.addObject(p)

        visitChildren(abstractSection)
    }

    override fun visit(keyWordBlock: KeyWordBlock) {
        val section = keyWordBlock.parentSection as AbstractSection
        val title = if (section.isChinese) {
            "关键字："
        } else {
            "Keywords: "
        }

        val sep = if (section.isChinese) {
            "，"
        } else {
            ", "
        }

        val p = createStyledParagraph("关键字")
        p.content.add(createTextR(title, bold = true))
        p.content.add(createTextR(keyWordBlock.keywords.joinToString(sep) { it }))
        mainDoc.addObject(p)
    }

    override fun visit(tocBlock: TocBlock) {
        mainDoc.addStyledParagraphOfText(resolveStyleId("heading 1"), "目录")
        val toc = objectFactory.createSdtBlock()

        tocIndex = mainDoc.content.size
        mainDoc.addObject(toc)

        val pr = objectFactory.createSdtPr()
        toc.sdtPr = pr

        val prEnd = objectFactory.createCTSdtEndPr()
        toc.sdtEndPr = prEnd

        val content = objectFactory.createSdtContentBlock()
        toc.sdtContent = content

        visitChildren(tocBlock)
    }

    override fun visit(thematicBreak: ThematicBreak) {
        val p = objectFactory.createP()
        mainDoc.addObject(p)

        val r = objectFactory.createR()
        p.content.add(r)

        val br = objectFactory.createBr()
        r.content.add(br)

        br.type = STBrType.PAGE
    }
}

class WordRenderer : DocumentRenderer {
    override fun render(doc: MarkdownDocument, option: RenderOption): File {
        val inputStream = if (option.templateFile == "") {
            javaClass.classLoader.getResourceAsStream("lab/cadl/lirui/markdown/core/template.docx")
        } else {
            Paths.get(option.templateFile).toFile().inputStream()
        }

        val mlPackage = Docx4J.load(inputStream)
        val targetFile = Paths.get(option.outputDir, option.originName + ".docx").toFile()

        mlPackage.mainDocumentPart.content.clear()
        val visitor = WordVisitor(mlPackage, option)
        visitor.visit(doc)

        if (visitor.tocIndex >= 0) {
            val tocGenerator = TocGenerator(mlPackage)
            tocGenerator.generateToc(visitor.tocIndex, "TOC \\0 \"1-3\" \\h \\z \\u ", true)
        }

        Docx4J.save(mlPackage, targetFile)

        return targetFile
    }

    override fun defaultCustomOption(): CustomOption {
        return WordOption()
    }
}